package com.ddd.mill.infrastructure.advice;

import com.alibaba.fastjson.JSONObject;
import com.ddd.mill.infrastructure.constant.BusinessErrorCode;
import com.ddd.mill.infrastructure.exception.BizException;
import com.ddd.mill.infrastructure.dto.CommonResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.util.StreamUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * <h1>全局异常处理</h1>
 * RestControllerAdvice: 组合注解, ControllerAdvice + ResponseBody, 是对 RestController 的功能增强
 */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionAdvice {


    @Value("${spring.application.name}")
    private String applicationName;
    @Value(("${eureka.instance.instance-id}"))
    private String eurekaClientId;
// TODO    @Value("${ding-talk-alarm-robot-access-token}")
//    private String dingTalkAlarmRobotAccessToken;

    /**
     * <h2>对 BizException 进行统一处理</h2>
     * ExceptionHandler: 可以对指定的异常进行拦截
     *
     * @param exception
     * @return 统一异常接口的响应
     */
    @ExceptionHandler(value = BizException.class)
    public CommonResponse<String> handleException(BizException exception) {
        CommonResponse<String> response = new CommonResponse<>(exception.getErrorCode(), exception.getErrorMessage());
        response.setData(null);
        return response;
    }

    /**
     * <h2>对 未知Exception 进行统一处理</h2>
     * ExceptionHandler: 可以对指定的异常进行拦截
     *
     * @param request
     * @param exception
     * @return 统一异常接口的响应
     */
    @ExceptionHandler(value = Exception.class)
    public CommonResponse<String> handleException(HttpServletRequest request,
                                                  Exception exception) throws IOException {
        log.error("系统异常:", exception);
        try {
            String parameters = queryParameters(request);
            log.error("系统异常:" + request.getRequestURI());
            //map转json
            log.error("系统异常:" + parameters);
        } catch (Exception e) {
            log.error("系统异常:" + e.getMessage());

        }


        // 优化: 定义不同类型的异常枚举(异常码和异常信息)
        CommonResponse<String> response = new CommonResponse<>(BusinessErrorCode.BUSINESS_ERROR, "unknown error");
        response.setData(exception.getLocalizedMessage());
        if (response.getData() == null) {
            // 打印错误堆栈信息
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            exception.printStackTrace(new PrintStream(baos));
            response.setData(baos.toString());
        }
//        // 备注:sql的错误,也会在这个地方被捕捉,SQLSyntaxErrorException
        // todo 发送未知异常事件
//        if (smsClient != null) {
//            HashMap map = new HashMap();
//            String content = exception.getMessage() + ",原因:" + trackToString(exception.getStackTrace());
//            map.put("content", content);
//            CommonResponse rsp = smsClient.postDingTalkAlarm(eurekaClientId, dingTalkAlarmRobotAccessToken, map);
//            log.info(rsp.toString());
//        }
        return response;
    }

    /**
     * 拦截@Valid字段验证异常、断言异常
     *
     * @param request
     * @param exception
     * @return 统一异常接口的响应
     */
    @ExceptionHandler(value = {BindException.class, IllegalArgumentException.class})
    public CommonResponse<String> handleValidException(HttpServletRequest request,
                                                       Exception exception) {
        CommonResponse<String> response = new CommonResponse<>(BusinessErrorCode.BUSINESS_ERROR, exception.getMessage());
        response.setData(null);
        return response;
    }

    /**
     * 校验请求体错误拦截处理
     *
     * @param exception 错误信息集合
     * @return 错误信息
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public CommonResponse<String> handleException(HttpServletRequest request,
                                                  MethodArgumentNotValidException exception) {
        BindingResult result = exception.getBindingResult();
        CommonResponse<String> response = new CommonResponse<>(BusinessErrorCode.BUSINESS_ERROR, "bussiness error");
        if (result.hasErrors()) {
            List<FieldError> allErrors = result.getFieldErrors();
            String errors = allErrors.stream()
                    .map(error -> error == null ? "null" : error.getField() + ":" + error.getDefaultMessage())
                    .collect(Collectors.joining(", "));
            response.setMessage(errors);
        }
        return response;
    }

//    /**
//     * <h2>对 SaasException 进行统一处理</h2>
//     * ExceptionHandler: 可以对指定的异常进行拦截
//     * @param request
//     * @param exception
//     * @return 统一异常接口的响应
//     */
//    @ExceptionHandler(value = CustomException.class)
//    public CommonResponse<Object> handleException(HttpServletRequest request,
//                                                  CustomException exception) {
//        CommonResponse<Object> response = new CommonResponse<>(exception.getErrCode(), exception.getMessage());
//        response.setData(exception.getData());
//        return response;
//    }

    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    public CommonResponse<Object> handleException(HttpRequestMethodNotSupportedException exception) {
        CommonResponse<Object> response = new CommonResponse<>(HttpStatus.METHOD_NOT_ALLOWED.value(), exception.getMessage());
        return response;
    }

    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
    public CommonResponse<Object> handleException(HttpMediaTypeNotSupportedException exception) {
        CommonResponse<Object> response = new CommonResponse<>(HttpStatus.UNSUPPORTED_MEDIA_TYPE.value(), exception.getMessage());
        return response;
    }

    @ExceptionHandler(HttpMediaTypeNotAcceptableException.class)
    @ResponseStatus(HttpStatus.NOT_ACCEPTABLE)
    public CommonResponse<Object> handleException(HttpMediaTypeNotAcceptableException exception) {
        CommonResponse<Object> response = new CommonResponse<>(HttpStatus.NOT_ACCEPTABLE.value(), exception.getMessage());
        return response;
    }

    @ExceptionHandler(MissingPathVariableException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public CommonResponse<Object> handleException(MissingPathVariableException exception) {
        CommonResponse<Object> response = new CommonResponse<>(HttpStatus.INTERNAL_SERVER_ERROR.value(), exception.getMessage());
        return response;
    }

    @ExceptionHandler(MissingServletRequestParameterException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public CommonResponse<Object> handleException(MissingServletRequestParameterException exception) {
        CommonResponse<Object> response = new CommonResponse<>(HttpStatus.BAD_REQUEST.value(), exception.getMessage());
        return response;
    }

    @ExceptionHandler(NoHandlerFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public CommonResponse<Object> handleException(NoHandlerFoundException exception) {
        CommonResponse<Object> response = new CommonResponse<>(HttpStatus.NOT_FOUND.value(), exception.getMessage());
        return response;
    }

    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public CommonResponse<Object> handleException(MethodArgumentTypeMismatchException exception) {
        CommonResponse<Object> response = new CommonResponse<>(HttpStatus.BAD_REQUEST.value(), exception.getMessage());
        return response;
    }

    @ExceptionHandler(HttpMessageNotReadableException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public CommonResponse<Object> handleException(HttpMessageNotReadableException exception) {
        CommonResponse<Object> response = new CommonResponse<>(HttpStatus.BAD_REQUEST.value(), exception.getMessage());
        return response;
    }

    private String trackToString(StackTraceElement[] stackTraceElements) {
        StringBuilder sb = new StringBuilder();
        for (StackTraceElement stackTraceElemen : stackTraceElements) {
            sb.append(stackTraceElemen.toString() + "\n");
        }
        return sb.toString();
    }

    private String queryParameters(HttpServletRequest request) throws IOException {
        String method = request.getMethod().toUpperCase();
        if (method.equals("GET")) {
            Map<String, String[]> map = request.getParameterMap();
            return JSONObject.toJSONString(map);
        } else if (method.equals("POST")) {
            String contentType = request.getHeader("content-type");
            if (contentType.contains("application/json")) {
                InputStream is = request.getInputStream();
                return StreamUtils.copyToString(is, Charset.forName("utf-8"));
            } else {
                // 上传文件之类的,暂时不管
            }
        }
        return null;
    }
}
